<template>
	<view class="u-upload" v-if="!disabled">
		<view
			v-if="showUploadList"
			class="u-list-item u-preview-wrap"
			v-for="(item, index) in lists"
			:key="index"
			:style="{
				width: $u.addUnit(width),
				height: $u.addUnit(height)
			}"
		>
			<view
				v-if="deletable"
				class="u-delete-icon"
				@tap.stop="deleteItem(index)"
				:style="{
					background: delBgColor
				}"
			>
				<u-icon class="u-icon" :name="delIcon" size="20" :color="delColor"></u-icon>
			</view>
			<u-line-progress
				v-if="showProgress && item.progress > 0 && !item.error"
				:show-percent="false"
				height="16"
				class="u-progress"
				:percent="item.progress"
			></u-line-progress>
			<view @tap.stop="retry(index)" v-if="item.error" class="u-error-btn">点击重试</view>
			<image @tap.stop="doPreviewImage(item.url || item.path, index)" class="u-preview-image" v-if="!item.isImage" :src="item.url || item.path" :mode="imageMode"></image>
		</view>
		<slot name="file" :file="lists"></slot>
		<view style="display: inline-block;" @tap="selectFile" v-if="maxCount > lists.length">
			<slot name="addBtn"></slot>
			<view
				v-if="!customBtn"
				class="u-list-item u-add-wrap"
				hover-class="u-add-wrap__hover"
				hover-stay-time="150"
				:style="{
					width: $u.addUnit(width),
					height: $u.addUnit(height)
				}"
			>
				<u-icon name="plus" class="u-add-btn" size="40"></u-icon>
				<view class="u-add-tips">{{ uploadText }}</view>
			</view>
		</view>
	</view>
</template>

<script>

export default {
	name: 'u-upload',
	props: {
		
		showUploadList: {
			type: Boolean,
			default: true
		},
		
		action: {
			type: String,
			default: ''
		},
		
		maxCount: {
			type: [String, Number],
			default: 52
		},
		
		showProgress: {
			type: Boolean,
			default: true
		},
		
		disabled: {
			type: Boolean,
			default: false
		},
		
		imageMode: {
			type: String,
			default: 'aspectFill'
		},
		
		header: {
			type: Object,
			default() {
				return {};
			}
		},
		
		formData: {
			type: Object,
			default() {
				return {};
			}
		},
		
		name: {
			type: String,
			default: 'file'
		},
		
		sizeType: {
			type: Array,
			default() {
				return ['original', 'compressed'];
			}
		},
		sourceType: {
			type: Array,
			default() {
				return ['album', 'camera'];
			}
		},
		
		previewFullImage: {
			type: Boolean,
			default: true
		},
		
		multiple: {
			type: Boolean,
			default: true
		},
		
		deletable: {
			type: Boolean,
			default: true
		},
		
		maxSize: {
			type: [String, Number],
			default: Number.MAX_VALUE
		},
		
		fileList: {
			type: Array,
			default() {
				return [];
			}
		},
		
		uploadText: {
			type: String,
			default: '选择图片'
		},
		
		autoUpload: {
			type: Boolean,
			default: true
		},
		
		showTips: {
			type: Boolean,
			default: true
		},
		
		customBtn: {
			type: Boolean,
			default: false
		},
		
		width: {
			type: [String, Number],
			default: 200
		},
		
		height: {
			type: [String, Number],
			default: 200
		},
		
		delBgColor: {
			type: String,
			default: '#fa3534'
		},
		
		delColor: {
			type: String,
			default: '#ffffff'
		},
		
		delIcon: {
			type: String,
			default: 'close'
		},
		
		toJson: {
			type: Boolean,
			default: true
		},
		
		beforeUpload: {
			type: Function,
			default: null
		},
		
		beforeRemove: {
			type: Function,
			default: null
		},
		
		limitType:{
			type: Array,
			default() {
				
				
				return ['png', 'jpg', 'jpeg', 'webp', 'gif', 'image'];
			}
		},
		
		index: {
			type: [Number, String],
			default: ''
		}
	},
	mounted() {},
	data() {
		return {
			lists: [],
			isInCount: true,
			uploading: false
		};
	},
	watch: {
		fileList: {
			immediate: true,
			handler(val) {
				val.map(value => {
					
					
					
					let tmp = this.lists.some(val => {
						return val.url == value.url;
					})
					
					!tmp && this.lists.push({ url: value.url, error: false, progress: 100 });
				});
			}
		},
		
		lists(n) {
			this.$emit('on-list-change', n, this.index);
		}
	},
	methods: {
		
		clear() {
			this.lists = [];
		},
		
		reUpload() {
			this.uploadFile();
		},
		
		selectFile() {
			if (this.disabled) return;
			const { name = '', maxCount, multiple, maxSize, sizeType, lists, camera, compressed, maxDuration, sourceType } = this;
			let chooseFile = null;
			const newMaxCount = maxCount - lists.length;
			
			chooseFile = new Promise((resolve, reject) => {
				uni.chooseImage({
					count: multiple ? (newMaxCount > 9 ? 9 : newMaxCount) : 1,
					sourceType: sourceType,
					sizeType,
					success: resolve,
					fail: reject
				});
			});
			chooseFile
				.then(res => {
					let file = null;
					let listOldLength = this.lists.length;
					res.tempFiles.map((val, index) => {
						
						if(!this.checkFileExt(val)) return ;
						
						
						if (!multiple && index >= 1) return;
						if (val.size > maxSize) {
							this.$emit('on-oversize', val, this.lists, this.index);
							this.showToast('超出允许的文件大小');
						} else {
							if (maxCount <= lists.length) {
								this.$emit('on-exceed', val, this.lists, this.index);
								this.showToast('超出最大允许的文件个数');
								return;
							}
							lists.push({
								url: val.path,
								progress: 0,
								error: false,
								file: val
							});
						}
					});
					
					this.$emit('on-choose-complete', this.lists, this.index);
					if (this.autoUpload) this.uploadFile(listOldLength);
				})
				.catch(error => {
					this.$emit('on-choose-fail', error);
				});
		},
		
		showToast(message, force = false) {
			if (this.showTips || force) {
				uni.showToast({
					title: message,
					icon: 'none'
				});
			}
		},
		
		upload() {
			this.uploadFile();
		},
		
		retry(index) {
			this.lists[index].progress = 0;
			this.lists[index].error = false;
			this.lists[index].response = null;
			uni.showLoading({
				title: '重新上传'
			});
			this.uploadFile(index);
		},
		
		async uploadFile(index = 0) {
			if (this.disabled) return;
			if (this.uploading) return;
			
			if (index >= this.lists.length) {
				this.$emit('on-uploaded', this.lists, this.index);
				return;
			}
			
			if (this.lists[index].progress == 100) {
				if (this.autoUpload == false) this.uploadFile(index + 1);
				return;
			}
			
			if(this.beforeUpload && typeof(this.beforeUpload) === 'function') {
				
				
				
				
				
				
				let beforeResponse = this.beforeUpload.bind(this.$u.$parent.call(this))(index, this.lists);
				
				if (!!beforeResponse && typeof beforeResponse.then === 'function') {
					await beforeResponse.then(res => {
						
					}).catch(err => {
						
						return this.uploadFile(index + 1);
					})
				} else if(beforeResponse === false) {
					
					return this.uploadFile(index + 1);
				} else {
					
				}
			}
			
			if (!this.action) {
				this.showToast('请配置上传地址', true);
				return;
			}
			this.lists[index].error = false;
			this.uploading = true;
			
			const task = uni.uploadFile({
				url: this.action,
				filePath: this.lists[index].url,
				name: this.name,
				formData: this.formData,
				header: this.header,
				success: res => {
					
					let data = this.toJson && this.$u.test.jsonString(res.data) ? JSON.parse(res.data) : res.data;
					if (![200, 201, 204].includes(res.statusCode)) {
						this.uploadError(index, data);
					} else {
						
						this.lists[index].response = data;
						this.lists[index].progress = 100;
						this.lists[index].error = false;
						this.$emit('on-success', data, index, this.lists, this.index);
					}
				},
				fail: e => {
					this.uploadError(index, e);
				},
				complete: res => {
					uni.hideLoading();
					this.uploading = false;
					this.uploadFile(index + 1);
					this.$emit('on-change', res, index, this.lists, this.index);
				}
			});
			task.onProgressUpdate(res => {
				if (res.progress > 0) {
					this.lists[index].progress = res.progress;
					this.$emit('on-progress', res, index, this.lists, this.index);
				}
			});
		},
		
		uploadError(index, err) {
			this.lists[index].progress = 0;
			this.lists[index].error = true;
			this.lists[index].response = null;
			this.$emit('on-error', err, index, this.lists, this.index);
			this.showToast('上传失败，请重试');
		},
		
		deleteItem(index) {
			uni.showModal({
				title: '提示',
				content: '您确定要删除此项吗？',
				success: async (res) => {
					if (res.confirm) {
						
						
						if(this.beforeRemove && typeof(this.beforeRemove) === 'function') {
							
							let beforeResponse = this.beforeRemove.bind(this.$u.$parent.call(this))(index, this.lists);
							
							if (!!beforeResponse && typeof beforeResponse.then === 'function') {
								await beforeResponse.then(res => {
									
									this.handlerDeleteItem(index);
								}).catch(err => {
									
									this.showToast('已终止移除');
								})
							} else if(beforeResponse === false) {
								
								this.showToast('已终止移除');
							} else {
								
								this.handlerDeleteItem(index);
							}
						} else {
							
							this.handlerDeleteItem(index);
						}
					}
				}
			});
		},
		
		handlerDeleteItem(index) {
			
			if (this.lists[index].process < 100 && this.lists[index].process > 0) {
				typeof this.lists[index].uploadTask != 'undefined' && this.lists[index].uploadTask.abort();
			}
			this.lists.splice(index, 1);
			this.$forceUpdate();
			this.$emit('on-remove', index, this.lists, this.index);
			this.showToast('移除成功');
		},
		
		remove(index) {
			
			if (index >= 0 && index < this.lists.length) {
				this.lists.splice(index, 1);
				this.$emit('on-list-change', this.lists, this.index);
			}
		},
		
		doPreviewImage(url, index) {
			if (!this.previewFullImage) return;
			const images = this.lists.map(item => item.url || item.path);
			uni.previewImage({
				urls: images,
				current: url,
				success: () => {
					this.$emit('on-preview', url, this.lists, this.index);
				},
				fail: () => {
					uni.showToast({
						title: '预览图片失败',
						icon: 'none'
					});
				}
			});
		},
		
		checkFileExt(file) {
			
			let noArrowExt = false;
			
			let fileExt = '';
			const reg = /.+\./;
			
			// #ifdef H5
			fileExt = file.name.replace(reg, "").toLowerCase();
			// #endif
			
			// #ifndef H5
			fileExt = file.path.replace(reg, "").toLowerCase();
			// #endif
			
			noArrowExt = this.limitType.some(ext => {
				
				return ext.toLowerCase() === fileExt;
			})
			if(!noArrowExt) this.showToast(`不允许选择${fileExt}格式的文件`);
			return noArrowExt;
		}
	}
};
</script>

<style lang="scss" scoped>
@import '../../libs/css/style.components.scss';

.u-upload {
	@include vue-flex;
	flex-wrap: wrap;
	align-items: center;
}

.u-list-item {
	width: 200rpx;
	height: 200rpx;
	overflow: hidden;
	margin: 10rpx;
	background: rgb(244, 245, 246);
	position: relative;
	border-radius: 10rpx;
	/* #ifndef APP-NVUE */
	display: flex;
	/* #endif */
	align-items: center;
	justify-content: center;
}

.u-preview-wrap {
	border: 1px solid rgb(235, 236, 238);
}

.u-add-wrap {
	flex-direction: column;
	color: $u-content-color;
	font-size: 26rpx;
}

.u-add-tips {
	margin-top: 20rpx;
	line-height: 40rpx;
}

.u-add-wrap__hover {
	background-color: rgb(235, 236, 238);
}

.u-preview-image {
	display: block;
	width: 100%;
	height: 100%;
	border-radius: 10rpx;
}

.u-delete-icon {
	position: absolute;
	top: 10rpx;
	right: 10rpx;
	z-index: 10;
	background-color: $u-type-error;
	border-radius: 100rpx;
	width: 44rpx;
	height: 44rpx;
	@include vue-flex;
	align-items: center;
	justify-content: center;
}

.u-icon {
	@include vue-flex;
	align-items: center;
	justify-content: center;
}

.u-progress {
	position: absolute;
	bottom: 10rpx;
	left: 8rpx;
	right: 8rpx;
	z-index: 9;
	width: auto;
}

.u-error-btn {
	color: #ffffff;
	background-color: $u-type-error;
	font-size: 20rpx;
	padding: 4px 0;
	text-align: center;
	position: absolute;
	bottom: 0;
	left: 0;
	right: 0;
	z-index: 9;
	line-height: 1;
}
</style>
